How well does the size of a Condominium in New York City Explain Sale Price?

This project will explore the following questions:

  1. How well does the size of a property (measured in gross square feet) explain or predict sale price across New York City as a whole?
  2. How well does the size of a property explain or predict sale price for each individual borough?

To answer question 1 we will explore property data for all five boroughs

To answer question 2 we will build, analyze and compare linear models for each borough

Understanding the data

The data that we are using from this project come from five separate excel files (one for each borough) and can be obtained from here

The data is for June 2019-May 2020

library(readxl)
library(readr)
library(stringr)
library(dplyr)
library(tidyr)
library(magrittr)
library(ggplot2)
# loading in the data from each excel file setting skip = 4 to avoid header rows
queens <- read_excel("rollingsales_queens.xls", skip = 4)
staten_island <- read_excel("rollingsales_statenisland.xls", skip = 4)
brooklyn <- read_excel("rollingsales_brooklyn.xls", skip = 4)
bronx <- read_excel("rollingsales_bronx.xls", skip = 4)
manhattan <- read_excel("rollingsales_manhattan.xls", skip = 4)

Each Borough in New York has a specific number which we can get from the BOROUGH variable in each dataframe

head(queens$BOROUGH, 1)
[1] "4"
head(staten_island$BOROUGH, 1)
[1] "5"
head(brooklyn$BOROUGH, 1)
[1] "3"
head(bronx$BOROUGH, 1)
[1] "2"
head(manhattan$BOROUGH, 1)
[1] "1"
# The Borough numbers are as follows:
# Manhattan = 1
# Bronx = 2
# Brooklyn = 3
# Queens = 4
# Staten Island = 5

We will now bind the five dataframes into a single dataframe

NYC_property_sales <- bind_rows(manhattan, bronx, brooklyn, queens, staten_island)

Since we have a single dataframe with all NYC property sales, we don’t need the 5 individual dataframes and can remove then using the rm function

rm(brooklyn, bronx, manhattan, queens, staten_island)

Since we have the numbers for each borough we can replace them with the borough names so that it is more clear

NYC_property_sales <- NYC_property_sales %>% mutate(BOROUGH = 
                                                      case_when(BOROUGH == 1 ~ "Manhattan",
                                                                BOROUGH == 2 ~ "Bronx",
                                                                BOROUGH == 3 ~ "Brooklyn",
                                                                BOROUGH == 4 ~ "Queens",
                                                                BOROUGH == 5 ~ "Staten Island"))

Now that we have our data in a single dataframe with the BOROUGH column taken care of there are a few more steps to get this into the format that we need

# Convert all column names to lowercase and replace spaces with underscores
colnames(NYC_property_sales) %<>% str_replace_all("\\s", "_") %>% tolower()

# Convert CAPTIALIZED case to Title case (the NEIGHBORHOOD,BUILDING CLASS CATEGORY and ADDRESS columns)
NYC_property_sales <- NYC_property_sales %>% mutate(neighborhood = str_to_title(neighborhood)) %>% mutate(building_class_category = str_to_title(building_class_category)) %>% mutate(address = str_to_title(address))
# Drop the ease-ment column as it has no data and only select distinct values
NYC_property_sales <- NYC_property_sales %>% select(-`ease-ment`) %>% distinct()

# Filter out property exchanges between family members with a threshold of 10,000
NYC_property_sales <- NYC_property_sales %>% filter(sale_price > 10000) %>%
# Remove properties that have gross_square_feet of 0 
filter(gross_square_feet > 0) %>% 
# Remove NA values in both sale_price and gross_square_feet
drop_na(c(gross_square_feet, sale_price))

# Arrange by borough and neighborhood
NYC_property_sales <- NYC_property_sales %>% arrange(borough, neighborhood)

We will save it to a csv file

write_csv(NYC_property_sales, "NYC_property_sales.csv")

As I found out for condominums gross_square_feet isn’t being collected for condominiums (this uses the latest data obtained on July 20th 2020) so we need to find out what building types we can use

sort(table(NYC_property_sales$building_class_at_present))

  GW   H7   H8   H9   I3   J2   M4   P7   Q2   Q8   W1   W6   Z3   Z5   C6   G3   G4   H4   HB   I1   I7   I9   J9   K3   K8   P5   P6   Q1   Q9 
   1    1    1    1    1    1    1    1    1    1    1    1    1    1    2    2    2    2    2    2    2    2    2    2    2    2    2    2    2 
  D8   G5   G8   H2   N2   S0   W3   W7   W8   D5   F2   P2   O3   P9   C9   GU   I6   M3   W9   K6   N9   A7   W2   H1   H3   K5   O9   E7   RR 
   3    3    3    3    3    3    3    3    3    4    4    4    5    5    6    6    6    6    6    7    7    8    8    9    9   10   10   11   11 
  E2   M9   D9   F1   I5   K7   O1   D3   F4   G9   D6   F9   D7   O4   O8   O6   O5   O2   A6   O7   E9   F5   M1   S4   S5   G2   G1   S3   Z9 
  12   13   14   15   16   16   16   17   20   20   21   21   23   28   30   31   32   34   35   35   43   44   45   48   50   52   53   56   56 
  S9   C5   D1   C4   K2   E1   A4   C7   K4   S1   K1   A3   C1   C2   A0   S2   C3   A9   B9   A2   C0   B3   B1   B2   A5   A1 
  61   66   70   71   85  100  115  122  138  139  173  207  222  230  272  323  363  707  766 1501 1714 1986 2321 2521 3406 4070 

The most listed is type A1 which according to the building codes is this: TWO STORIES - DETACHED SM OR MID

According to further research found here it is a single familiy home and thus we will need to include land_square_feet

NYC_property_sales <- NYC_property_sales %>% filter(land_square_feet > 0) %>% drop_na(land_square_feet)

We will now filter according to A1

NYC_sm_md_two_story <- NYC_property_sales %>% filter(building_class_at_time_of_sale == "A1")
head(NYC_sm_md_two_story)

Explore Bivarate Relationships with Scatterplots

ggplot(data = NYC_sm_md_two_story,
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
       aes(x = gross_square_feet, y = sale_price)) +
  geom_point(aes(color = borough), alpha = 0.3) +
  scale_y_continuous(labels = scales::comma, limits = c(0,10000000)) +
  xlim(0, 10000) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(title = "Single Family Home Sale Price increases with Size",
       x = "Size (Gross Square Feet)",
       y = "Sale Price (USD)")

Let’s adjust our y limits by looking at the maximum sale price

max(NYC_sm_md_two_story$sale_price)
[1] 9e+06

We see in general as gross square feet increases sale price increases

Let’s look at land square feet to see if that holds true

ggplot(data = NYC_sm_md_two_story,
       aes(x = land_square_feet, y = sale_price)) +
  geom_point(aes(color = borough), alpha = 0.3) +
  scale_y_continuous(labels = scales::comma, limits = c(0,10000000)) +
  xlim(0, 10000) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(title = "Single Family Home Sale Price increases with Size",
       x = "Land Size (Square Feet)",
       y = "Sale Price (USD)")

We see this also holds up for land square feet (which is included in gross square feet as homes include the square footage of the house as well as land)

We will also zoom in on a smaller subset of data with a max sale price of $2,500,000 and a max gross square feet of 5000

ggplot(data = NYC_sm_md_two_story,
       aes(x = gross_square_feet, y = sale_price)) +
  geom_point(aes(color = borough), alpha = 0.3) +
  scale_y_continuous(labels = scales::comma, limits = c(0,2500000)) +
  xlim(0, 5000) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(title = "Single Family Home Sale Price increases with Size",
       x = "Size (Gross Square Feet)",
       y = "Sale Price (USD)")

Zooming in also shows for single family homes as the size increases so does the price

Looking at land values

ggplot(data = NYC_sm_md_two_story,
       aes(x = land_square_feet, y = sale_price)) +
  geom_point(aes(color = borough), alpha = 0.3) +
  scale_y_continuous(labels = scales::comma, limits = c(0,2500000)) +
  xlim(0, 5000) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(title = "Single Family Home Sale Price increases with Size",
       x = "Land Size (Square Feet)",
       y = "Sale Price (USD)")

This trend also shows that as land size increases the sale price also increaes

However, these graphs are cluttered in that all five boroughs are shown.

ggplot(data = NYC_sm_md_two_story,
       aes(x = gross_square_feet, y = sale_price)) +
  geom_point(alpha = 0.3) +
  facet_wrap(~ borough, scales = "free", ncol = 2) +
  scale_y_continuous(labels = scales::comma) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(title = "Single Family Home Sale Price increases with Size",
       x = "Size (Gross Square Feet)",
       y = "Sale Price (USD)")

For each borough we see that homes with larger gross square feet have higher sale prices

Outliers and Data Integrity Issues

To see any outliers we first need to sort sale prices by highest to lowest

NYC_sm_md_two_story %>% arrange(desc(sale_price)) %>% head()
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls

5 of the top sellers are in Brooklyn with one in Queens. The highest price of $9,000,000 was mostly land (looking on realtor.com doesn’t show a house but zillow.com does show a house there)

NYC_sm_md_two_story %>% filter(borough == "Brooklyn") %>% arrange(desc(gross_square_feet)) %>% head()

The largest gross square foot property was converted from a 3 familiy apartment to a single family home

NYC_sm_md_two_story %>% arrange(desc(gross_square_feet)) %>% head()

However looking at gross square feet here shows a different story but then it depends on the borough the property is in.

We will not remove the outlier for now as we want to confirm the sale is legitimate

Linear Regression Model for Boroughs in New York City Combined

We will now generate a linear model using sale_price explained by gross_square_feet and land_square_feet

NYC_sm_md_two_story_lm <- lm(sale_price ~ gross_square_feet + land_square_feet, data = NYC_sm_md_two_story)
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
summary(NYC_sm_md_two_story_lm)

Call:
lm(formula = sale_price ~ gross_square_feet + land_square_feet, 
    data = NYC_sm_md_two_story)

Residuals:
     Min       1Q   Median       3Q      Max 
-1491507  -157622   -24579   108987  6836803 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       1.346e+05  1.429e+04   9.420   <2e-16 ***
gross_square_feet 3.272e+02  9.367e+00  34.932   <2e-16 ***
land_square_feet  6.489e+00  2.563e+00   2.532   0.0114 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 321300 on 4076 degrees of freedom
Multiple R-squared:  0.2975,    Adjusted R-squared:  0.2972 
F-statistic: 863.2 on 2 and 4076 DF,  p-value: < 2.2e-16

If we remove the highest listing

NYC_sm_md_two_story_modified <- NYC_sm_md_two_story %>% filter(address != "704 Avenue I")
NYC_sm_md_two_story_modified_lm <- lm(sale_price ~ gross_square_feet + land_square_feet, data = NYC_sm_md_two_story_modified)

summary(NYC_sm_md_two_story_modified_lm)

Call:
lm(formula = sale_price ~ gross_square_feet + land_square_feet, 
    data = NYC_sm_md_two_story_modified)

Residuals:
     Min       1Q   Median       3Q      Max 
-1369545  -155512   -26950   106644  3692512 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       1.635e+05  1.352e+04  12.087  < 2e-16 ***
gross_square_feet 3.027e+02  8.888e+00  34.061  < 2e-16 ***
land_square_feet  8.960e+00  2.416e+00   3.708 0.000212 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 302600 on 4075 degrees of freedom
Multiple R-squared:  0.2955,    Adjusted R-squared:  0.2951 
F-statistic: 854.6 on 2 and 4075 DF,  p-value: < 2.2e-16

Removing the highest listing caused a reduction in the estimate for gross_square_feet but increased land_square_feet. For standard error it was reduced for both gross_square_feet and land_square_feet

Replotting using facet wrap without the outlier

ggplot(data = NYC_sm_md_two_story_modified,
       aes(x = gross_square_feet, y = sale_price)) +
  geom_point(alpha = 0.3) +
  facet_wrap(~ borough, scales = "free", ncol = 2) +
  scale_y_continuous(labels = scales::comma) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(title = "Single Family Home Sale Price increases with Size",
       x = "Size (Gross Square Feet)",
       y = "Sale Price (USD)")

Linear regression model for each borough - Coefficient Estimates

We have a linear model for all of the boroughs but would be more useful is if we had a model for each one. We will use the dataset that contains the outlier as that is still a sale (and was confirmed through realtor.com)

We will use the broom workflow which is the following:

  1. Nest a dataframe by a categorical variable with the nest() function from the tidyr package - we will nest by borough.
  2. Fit models to nested dataframes with the map() function from the purrr package.
  3. Apply the broom functions tidy(), augment(), and/or glance() using each nested model - we’ll work with tidy() first.
  4. Return a tidy dataframe with the unnest() function - this allows us to see the results.
library(broom)
library(tidyr)
library(purrr)

Attaching package: ă¤¼ă¸±purrră¤¼ă¸²

The following object is masked from ă¤¼ă¸±package:magrittră¤¼ă¸²:

    set_names
NYC_sm_md_two_story_nested <- NYC_sm_md_two_story %>% group_by(borough) %>% nest()
print(NYC_sm_md_two_story_nested)

Looking at Manhattan we have

head(NYC_sm_md_two_story_nested$data[[3]])

We will now fit a linear model to each dataframe (borough)

NYC_sm_md_two_story_coefficients <- NYC_sm_md_two_story %>% group_by(borough) %>% nest() %>%
  mutate(linear_model = map(.x = data,
                            .f = ~lm(sale_price ~ gross_square_feet + land_square_feet, data = .)
                            ))
print(NYC_sm_md_two_story_coefficients)

Looking at the summary for Manhattan

summary(NYC_sm_md_two_story_coefficients$linear_model[[3]])

Call:
lm(formula = sale_price ~ gross_square_feet + land_square_feet, 
    data = .)

Residuals:
ALL 1 residuals are 0: no residual degrees of freedom!

Coefficients: (2 not defined because of singularities)
                  Estimate Std. Error t value Pr(>|t|)
(Intercept)         649000         NA      NA       NA
gross_square_feet       NA         NA      NA       NA
land_square_feet        NA         NA      NA       NA

Residual standard error: NaN on 0 degrees of freedom

That is because there is only a single entry for Manhattan looking at Brooklyn

summary(NYC_sm_md_two_story_coefficients$linear_model[[2]])

Call:
lm(formula = sale_price ~ gross_square_feet + land_square_feet, 
    data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-1514579  -244635   -31472   187012  5551040 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)       -108124.95   67684.87  -1.597   0.1108    
gross_square_feet     519.06      39.73  13.063   <2e-16 ***
land_square_feet       66.31      26.35   2.516   0.0122 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 516800 on 494 degrees of freedom
Multiple R-squared:  0.4289,    Adjusted R-squared:  0.4265 
F-statistic: 185.5 on 2 and 494 DF,  p-value: < 2.2e-16

Transforming this dataframe into a tidy format

NYC_sm_md_two_story_coefficients <- NYC_sm_md_two_story %>% group_by(borough) %>% nest() %>%
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
6: In readChar(file, size, TRUE) : truncating string with embedded nuls
  mutate(linear_model = map(.x = data,
                            .f = ~lm(sale_price ~ gross_square_feet + land_square_feet, data = .)
                            )) %>%
  mutate(tidy_coefficients = map(.x = linear_model,
                                 .f = tidy,
                                 conf.int = TRUE))
NaNs produced
NYC_sm_md_two_story_coefficients

First inspect for Brooklyn

print(NYC_sm_md_two_story_coefficients$tidy_coefficients[[2]])

And let’s get a tidy dataframe out using unest()

NYC_sm_md_two_story_tidy <- NYC_sm_md_two_story_coefficients %>% 
  select(borough, tidy_coefficients) %>% 
  unnest(cols = tidy_coefficients)

print(NYC_sm_md_two_story_tidy)

Gross square feet has a higher estimate but our models include both as these are homes and not condominiums. However, we will use gross square feet as that has a higher estimate

NYC_sm_md_two_story_slope <- NYC_sm_md_two_story_tidy %>% filter(term == "gross_square_feet") %>% arrange(estimate)
print(NYC_sm_md_two_story_slope)

The highest estimate for gross square feet is in Brooklyn (which also has the highest sale price)

Looking at land square feet

NYC_sm_md_two_story_slope_land <- NYC_sm_md_two_story_tidy %>% filter(term == "land_square_feet") %>% arrange(estimate)
print(NYC_sm_md_two_story_slope_land)

This also confirms that brooklyn has the highest estimate for land square feet

Linear Regression Models for each Borough - Regression Summary Statistics

We will apply the same workflow to generate tidy summary statistics for each of the five boroughs

NYC_sm_md_two_story_summary <- NYC_sm_md_two_story %>% 
There were 24 warnings (use warnings() to see them)
  group_by(borough) %>%
  nest() %>% mutate(linear_model = map(.x = data,
                                       .f = ~lm(sale_price ~ gross_square_feet + land_square_feet, data = .))) %>%
  mutate(tidy_summary_stats = map(.x = linear_model,
                                  .f = glance))

print(NYC_sm_md_two_story_summary)
NA

Now let’s use the tidy_summary_stats to get our conclusion

NYC_sm_md_two_story_tidy <- NYC_sm_md_two_story_summary %>% 
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
6: In readChar(file, size, TRUE) : truncating string with embedded nuls
  select(borough, tidy_summary_stats) %>%
  unnest(cols = tidy_summary_stats) %>%
  arrange(r.squared)

print(NYC_sm_md_two_story_tidy)

Conclusion

We can see that gross square feet and land square feet are good predictors of sales prices with the highest R squared value in Brooklyn followed by Staten Island and the Bronx. This is the lower in Queens and Manhattan has a value of 0 simply because there was a single A1 property sale there.

LS0tDQp0aXRsZTogIkd1aWRlZCBQcm9qZWN0OiBQcmVkaWN0aW5nIENvbmRvbWluaXVtIFNhbGUgUHJpY2VzIg0KYXV0aG9yOiAiTW9iaW4gQW5hbmR3YWxhIg0KZGF0ZTogIkp1bHkgMTQgMjAyMCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgSG93IHdlbGwgZG9lcyB0aGUgc2l6ZSBvZiBhIENvbmRvbWluaXVtIGluIE5ldyBZb3JrIENpdHkgRXhwbGFpbiBTYWxlIFByaWNlPw0KDQpUaGlzIHByb2plY3Qgd2lsbCBleHBsb3JlIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zOg0KDQoxLiBIb3cgd2VsbCBkb2VzIHRoZSBzaXplIG9mIGEgcHJvcGVydHkgKG1lYXN1cmVkIGluIGdyb3NzIHNxdWFyZSBmZWV0KSBleHBsYWluIG9yIHByZWRpY3Qgc2FsZSBwcmljZSBhY3Jvc3MgTmV3IFlvcmsgQ2l0eSBhcyBhIHdob2xlPyANCjIuIEhvdyB3ZWxsIGRvZXMgdGhlIHNpemUgb2YgYSBwcm9wZXJ0eSBleHBsYWluIG9yIHByZWRpY3Qgc2FsZSBwcmljZSBmb3IgZWFjaCBpbmRpdmlkdWFsIGJvcm91Z2g/DQoNCg0KVG8gYW5zd2VyIHF1ZXN0aW9uIDEgd2Ugd2lsbCBleHBsb3JlIHByb3BlcnR5IGRhdGEgZm9yIGFsbCBmaXZlIGJvcm91Z2hzDQoNClRvIGFuc3dlciBxdWVzdGlvbiAyIHdlIHdpbGwgYnVpbGQsIGFuYWx5emUgYW5kIGNvbXBhcmUgbGluZWFyIG1vZGVscyBmb3IgZWFjaCBib3JvdWdoDQoNCiMgVW5kZXJzdGFuZGluZyB0aGUgZGF0YQ0KDQpUaGUgZGF0YSB0aGF0IHdlIGFyZSB1c2luZyBmcm9tIHRoaXMgcHJvamVjdCBjb21lIGZyb20gZml2ZSBzZXBhcmF0ZSBleGNlbCBmaWxlcyAob25lIGZvciBlYWNoIGJvcm91Z2gpIGFuZCBjYW4gYmUgb2J0YWluZWQgZnJvbSBbaGVyZV0oaHR0cHM6Ly93d3cxLm55Yy5nb3Yvc2l0ZS9maW5hbmNlL3RheGVzL3Byb3BlcnR5LXJvbGxpbmctc2FsZXMtZGF0YS5wYWdlKQ0KDQpUaGUgZGF0YSBpcyBmb3IgSnVuZSAyMDE5LU1heSAyMDIwDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KYGBge3J9DQojIGxvYWRpbmcgaW4gdGhlIGRhdGEgZnJvbSBlYWNoIGV4Y2VsIGZpbGUgc2V0dGluZyBza2lwID0gNCB0byBhdm9pZCBoZWFkZXIgcm93cw0KcXVlZW5zIDwtIHJlYWRfZXhjZWwoInJvbGxpbmdzYWxlc19xdWVlbnMueGxzIiwgc2tpcCA9IDQpDQpzdGF0ZW5faXNsYW5kIDwtIHJlYWRfZXhjZWwoInJvbGxpbmdzYWxlc19zdGF0ZW5pc2xhbmQueGxzIiwgc2tpcCA9IDQpDQpicm9va2x5biA8LSByZWFkX2V4Y2VsKCJyb2xsaW5nc2FsZXNfYnJvb2tseW4ueGxzIiwgc2tpcCA9IDQpDQpicm9ueCA8LSByZWFkX2V4Y2VsKCJyb2xsaW5nc2FsZXNfYnJvbngueGxzIiwgc2tpcCA9IDQpDQptYW5oYXR0YW4gPC0gcmVhZF9leGNlbCgicm9sbGluZ3NhbGVzX21hbmhhdHRhbi54bHMiLCBza2lwID0gNCkNCmBgYA0KDQpFYWNoIEJvcm91Z2ggaW4gTmV3IFlvcmsgaGFzIGEgc3BlY2lmaWMgbnVtYmVyIHdoaWNoIHdlIGNhbiBnZXQgZnJvbSB0aGUgYEJPUk9VR0hgIHZhcmlhYmxlIGluIGVhY2ggZGF0YWZyYW1lDQoNCmBgYHtyfQ0KaGVhZChxdWVlbnMkQk9ST1VHSCwgMSkNCmhlYWQoc3RhdGVuX2lzbGFuZCRCT1JPVUdILCAxKQ0KaGVhZChicm9va2x5biRCT1JPVUdILCAxKQ0KaGVhZChicm9ueCRCT1JPVUdILCAxKQ0KaGVhZChtYW5oYXR0YW4kQk9ST1VHSCwgMSkNCmBgYA0KDQpgYGB7cn0NCiMgVGhlIEJvcm91Z2ggbnVtYmVycyBhcmUgYXMgZm9sbG93czoNCiMgTWFuaGF0dGFuID0gMQ0KIyBCcm9ueCA9IDINCiMgQnJvb2tseW4gPSAzDQojIFF1ZWVucyA9IDQNCiMgU3RhdGVuIElzbGFuZCA9IDUNCmBgYA0KDQpXZSB3aWxsIG5vdyBiaW5kIHRoZSBmaXZlIGRhdGFmcmFtZXMgaW50byBhIHNpbmdsZSBkYXRhZnJhbWUNCmBgYHtyfQ0KTllDX3Byb3BlcnR5X3NhbGVzIDwtIGJpbmRfcm93cyhtYW5oYXR0YW4sIGJyb254LCBicm9va2x5biwgcXVlZW5zLCBzdGF0ZW5faXNsYW5kKQ0KYGBgDQoNClNpbmNlIHdlIGhhdmUgYSBzaW5nbGUgZGF0YWZyYW1lIHdpdGggYWxsIE5ZQyBwcm9wZXJ0eSBzYWxlcywgd2UgZG9uJ3QgbmVlZCB0aGUgNSBpbmRpdmlkdWFsIGRhdGFmcmFtZXMgYW5kIGNhbiByZW1vdmUgdGhlbiB1c2luZyB0aGUgYHJtYCBmdW5jdGlvbg0KYGBge3J9DQpybShicm9va2x5biwgYnJvbngsIG1hbmhhdHRhbiwgcXVlZW5zLCBzdGF0ZW5faXNsYW5kKQ0KYGBgDQoNClNpbmNlIHdlIGhhdmUgdGhlIG51bWJlcnMgZm9yIGVhY2ggYm9yb3VnaCB3ZSBjYW4gcmVwbGFjZSB0aGVtIHdpdGggdGhlIGJvcm91Z2ggbmFtZXMgc28gdGhhdCBpdCBpcyBtb3JlIGNsZWFyDQpgYGB7cn0NCk5ZQ19wcm9wZXJ0eV9zYWxlcyA8LSBOWUNfcHJvcGVydHlfc2FsZXMgJT4lIG11dGF0ZShCT1JPVUdIID0gDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYXNlX3doZW4oQk9ST1VHSCA9PSAxIH4gIk1hbmhhdHRhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQk9ST1VHSCA9PSAyIH4gIkJyb254IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCT1JPVUdIID09IDMgfiAiQnJvb2tseW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJPUk9VR0ggPT0gNCB+ICJRdWVlbnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJPUk9VR0ggPT0gNSB+ICJTdGF0ZW4gSXNsYW5kIikpDQoNCmBgYA0KDQpOb3cgdGhhdCB3ZSBoYXZlIG91ciBkYXRhIGluIGEgc2luZ2xlIGRhdGFmcmFtZSB3aXRoIHRoZSBgQk9ST1VHSGAgY29sdW1uIHRha2VuIGNhcmUgb2YgdGhlcmUgYXJlIGEgZmV3IG1vcmUgc3RlcHMgdG8gZ2V0IHRoaXMgaW50byB0aGUgZm9ybWF0IHRoYXQgd2UgbmVlZA0KYGBge3J9DQojIENvbnZlcnQgYWxsIGNvbHVtbiBuYW1lcyB0byBsb3dlcmNhc2UgYW5kIHJlcGxhY2Ugc3BhY2VzIHdpdGggdW5kZXJzY29yZXMNCmNvbG5hbWVzKE5ZQ19wcm9wZXJ0eV9zYWxlcykgJTw+JSBzdHJfcmVwbGFjZV9hbGwoIlxccyIsICJfIikgJT4lIHRvbG93ZXIoKQ0KDQojIENvbnZlcnQgQ0FQVElBTElaRUQgY2FzZSB0byBUaXRsZSBjYXNlICh0aGUgTkVJR0hCT1JIT09ELEJVSUxESU5HIENMQVNTIENBVEVHT1JZIGFuZCBBRERSRVNTIGNvbHVtbnMpDQpOWUNfcHJvcGVydHlfc2FsZXMgPC0gTllDX3Byb3BlcnR5X3NhbGVzICU+JSBtdXRhdGUobmVpZ2hib3Job29kID0gc3RyX3RvX3RpdGxlKG5laWdoYm9yaG9vZCkpICU+JSBtdXRhdGUoYnVpbGRpbmdfY2xhc3NfY2F0ZWdvcnkgPSBzdHJfdG9fdGl0bGUoYnVpbGRpbmdfY2xhc3NfY2F0ZWdvcnkpKSAlPiUgbXV0YXRlKGFkZHJlc3MgPSBzdHJfdG9fdGl0bGUoYWRkcmVzcykpDQpgYGANCg0KDQpgYGB7cn0NCiMgRHJvcCB0aGUgZWFzZS1tZW50IGNvbHVtbiBhcyBpdCBoYXMgbm8gZGF0YSBhbmQgb25seSBzZWxlY3QgZGlzdGluY3QgdmFsdWVzDQpOWUNfcHJvcGVydHlfc2FsZXMgPC0gTllDX3Byb3BlcnR5X3NhbGVzICU+JSBzZWxlY3QoLWBlYXNlLW1lbnRgKSAlPiUgZGlzdGluY3QoKQ0KDQojIEZpbHRlciBvdXQgcHJvcGVydHkgZXhjaGFuZ2VzIGJldHdlZW4gZmFtaWx5IG1lbWJlcnMgd2l0aCBhIHRocmVzaG9sZCBvZiAxMCwwMDANCk5ZQ19wcm9wZXJ0eV9zYWxlcyA8LSBOWUNfcHJvcGVydHlfc2FsZXMgJT4lIGZpbHRlcihzYWxlX3ByaWNlID4gMTAwMDApICU+JQ0KIyBSZW1vdmUgcHJvcGVydGllcyB0aGF0IGhhdmUgZ3Jvc3Nfc3F1YXJlX2ZlZXQgb2YgMCANCmZpbHRlcihncm9zc19zcXVhcmVfZmVldCA+IDApICU+JSANCiMgUmVtb3ZlIE5BIHZhbHVlcyBpbiBib3RoIHNhbGVfcHJpY2UgYW5kIGdyb3NzX3NxdWFyZV9mZWV0DQpkcm9wX25hKGMoZ3Jvc3Nfc3F1YXJlX2ZlZXQsIHNhbGVfcHJpY2UpKQ0KDQojIEFycmFuZ2UgYnkgYm9yb3VnaCBhbmQgbmVpZ2hib3Job29kDQpOWUNfcHJvcGVydHlfc2FsZXMgPC0gTllDX3Byb3BlcnR5X3NhbGVzICU+JSBhcnJhbmdlKGJvcm91Z2gsIG5laWdoYm9yaG9vZCkNCmBgYA0KDQpXZSB3aWxsIHNhdmUgaXQgdG8gYSBjc3YgZmlsZQ0KYGBge3IgZXZhbD0gRkFMU0V9DQp3cml0ZV9jc3YoTllDX3Byb3BlcnR5X3NhbGVzLCAiTllDX3Byb3BlcnR5X3NhbGVzLmNzdiIpDQpgYGANCg0KQXMgSSBmb3VuZCBvdXQgZm9yIGNvbmRvbWludW1zIGBncm9zc19zcXVhcmVfZmVldGAgaXNuJ3QgYmVpbmcgY29sbGVjdGVkIGZvciBjb25kb21pbml1bXMgKHRoaXMgdXNlcyB0aGUgbGF0ZXN0IGRhdGEgb2J0YWluZWQgb24gSnVseSAyMHRoIDIwMjApIHNvIHdlIG5lZWQgdG8gZmluZCBvdXQgd2hhdCBidWlsZGluZyB0eXBlcyB3ZSBjYW4gdXNlDQoNCmBgYHtyfQ0Kc29ydCh0YWJsZShOWUNfcHJvcGVydHlfc2FsZXMkYnVpbGRpbmdfY2xhc3NfYXRfcHJlc2VudCkpDQpgYGANCg0KVGhlIG1vc3QgbGlzdGVkIGlzIHR5cGUgQTEgd2hpY2ggYWNjb3JkaW5nIHRvIHRoZSBidWlsZGluZyBjb2RlcyBpcyB0aGlzOiBUV08gU1RPUklFUyAtIERFVEFDSEVEIFNNIE9SIE1JRA0KDQpBY2NvcmRpbmcgdG8gZnVydGhlciByZXNlYXJjaCBmb3VuZCBbaGVyZV0oaHR0cHM6Ly93d3cucHJvcGVydHlzaGFyay5jb20vaW5mby9jbGFzcy1idWlsZGluZ3Mtb25lLWZhbWlseS1kd2VsbGluZ3MvI2ExKSBpdCBpcyBhIHNpbmdsZSBmYW1pbGl5IGhvbWUgYW5kIHRodXMgd2Ugd2lsbCBuZWVkIHRvIGluY2x1ZGUgbGFuZF9zcXVhcmVfZmVldA0KDQpgYGB7cn0NCk5ZQ19wcm9wZXJ0eV9zYWxlcyA8LSBOWUNfcHJvcGVydHlfc2FsZXMgJT4lIGZpbHRlcihsYW5kX3NxdWFyZV9mZWV0ID4gMCkgJT4lIGRyb3BfbmEobGFuZF9zcXVhcmVfZmVldCkNCmBgYA0KDQpXZSB3aWxsIG5vdyBmaWx0ZXIgYWNjb3JkaW5nIHRvIEExDQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnkgPC0gTllDX3Byb3BlcnR5X3NhbGVzICU+JSBmaWx0ZXIoYnVpbGRpbmdfY2xhc3NfYXRfdGltZV9vZl9zYWxlID09ICJBMSIpDQpoZWFkKE5ZQ19zbV9tZF90d29fc3RvcnkpDQpgYGANCg0KIyBFeHBsb3JlIEJpdmFyYXRlIFJlbGF0aW9uc2hpcHMgd2l0aCBTY2F0dGVycGxvdHMNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IE5ZQ19zbV9tZF90d29fc3RvcnksDQogICAgICAgYWVzKHggPSBncm9zc19zcXVhcmVfZmVldCwgeSA9IHNhbGVfcHJpY2UpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYm9yb3VnaCksIGFscGhhID0gMC4zKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hLCBsaW1pdHMgPSBjKDAsMTAwMDAwMDApKSArDQogIHhsaW0oMCwgMTAwMDApICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlNpbmdsZSBGYW1pbHkgSG9tZSBTYWxlIFByaWNlIGluY3JlYXNlcyB3aXRoIFNpemUiLA0KICAgICAgIHggPSAiU2l6ZSAoR3Jvc3MgU3F1YXJlIEZlZXQpIiwNCiAgICAgICB5ID0gIlNhbGUgUHJpY2UgKFVTRCkiKQ0KYGBgDQoNCkxldCdzIGFkanVzdCBvdXIgeSBsaW1pdHMgYnkgbG9va2luZyBhdCB0aGUgbWF4aW11bSBzYWxlIHByaWNlDQpgYGB7cn0NCm1heChOWUNfc21fbWRfdHdvX3N0b3J5JHNhbGVfcHJpY2UpDQpgYGANCg0KV2Ugc2VlIGluIGdlbmVyYWwgYXMgZ3Jvc3Mgc3F1YXJlIGZlZXQgaW5jcmVhc2VzIHNhbGUgcHJpY2UgaW5jcmVhc2VzDQoNCkxldCdzIGxvb2sgYXQgbGFuZCBzcXVhcmUgZmVldCB0byBzZWUgaWYgdGhhdCBob2xkcyB0cnVlDQpgYGB7cn0NCmdncGxvdChkYXRhID0gTllDX3NtX21kX3R3b19zdG9yeSwNCiAgICAgICBhZXMoeCA9IGxhbmRfc3F1YXJlX2ZlZXQsIHkgPSBzYWxlX3ByaWNlKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGJvcm91Z2gpLCBhbHBoYSA9IDAuMykgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSwgbGltaXRzID0gYygwLDEwMDAwMDAwKSkgKw0KICB4bGltKDAsIDEwMDAwKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJTaW5nbGUgRmFtaWx5IEhvbWUgU2FsZSBQcmljZSBpbmNyZWFzZXMgd2l0aCBTaXplIiwNCiAgICAgICB4ID0gIkxhbmQgU2l6ZSAoU3F1YXJlIEZlZXQpIiwNCiAgICAgICB5ID0gIlNhbGUgUHJpY2UgKFVTRCkiKQ0KYGBgDQoNCldlIHNlZSB0aGlzIGFsc28gaG9sZHMgdXAgZm9yIGxhbmQgc3F1YXJlIGZlZXQgKHdoaWNoIGlzIGluY2x1ZGVkIGluIGdyb3NzIHNxdWFyZSBmZWV0IGFzIGhvbWVzIGluY2x1ZGUgdGhlIHNxdWFyZSBmb290YWdlIG9mIHRoZSBob3VzZSBhcyB3ZWxsIGFzIGxhbmQpDQoNCldlIHdpbGwgYWxzbyB6b29tIGluIG9uIGEgc21hbGxlciBzdWJzZXQgb2YgZGF0YSB3aXRoIGEgbWF4IHNhbGUgcHJpY2Ugb2YgXCQyLDUwMCwwMDAgYW5kIGEgbWF4IGdyb3NzIHNxdWFyZSBmZWV0IG9mIDUwMDANCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBOWUNfc21fbWRfdHdvX3N0b3J5LA0KICAgICAgIGFlcyh4ID0gZ3Jvc3Nfc3F1YXJlX2ZlZXQsIHkgPSBzYWxlX3ByaWNlKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGJvcm91Z2gpLCBhbHBoYSA9IDAuMykgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSwgbGltaXRzID0gYygwLDI1MDAwMDApKSArDQogIHhsaW0oMCwgNTAwMCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiU2luZ2xlIEZhbWlseSBIb21lIFNhbGUgUHJpY2UgaW5jcmVhc2VzIHdpdGggU2l6ZSIsDQogICAgICAgeCA9ICJTaXplIChHcm9zcyBTcXVhcmUgRmVldCkiLA0KICAgICAgIHkgPSAiU2FsZSBQcmljZSAoVVNEKSIpDQpgYGANCg0KWm9vbWluZyBpbiBhbHNvIHNob3dzIGZvciBzaW5nbGUgZmFtaWx5IGhvbWVzIGFzIHRoZSBzaXplIGluY3JlYXNlcyBzbyBkb2VzIHRoZSBwcmljZQ0KDQpMb29raW5nIGF0IGxhbmQgdmFsdWVzDQpgYGB7cn0NCmdncGxvdChkYXRhID0gTllDX3NtX21kX3R3b19zdG9yeSwNCiAgICAgICBhZXMoeCA9IGxhbmRfc3F1YXJlX2ZlZXQsIHkgPSBzYWxlX3ByaWNlKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGJvcm91Z2gpLCBhbHBoYSA9IDAuMykgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSwgbGltaXRzID0gYygwLDI1MDAwMDApKSArDQogIHhsaW0oMCwgNTAwMCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiU2luZ2xlIEZhbWlseSBIb21lIFNhbGUgUHJpY2UgaW5jcmVhc2VzIHdpdGggU2l6ZSIsDQogICAgICAgeCA9ICJMYW5kIFNpemUgKFNxdWFyZSBGZWV0KSIsDQogICAgICAgeSA9ICJTYWxlIFByaWNlIChVU0QpIikNCmBgYA0KDQpUaGlzIHRyZW5kIGFsc28gc2hvd3MgdGhhdCBhcyBsYW5kIHNpemUgaW5jcmVhc2VzIHRoZSBzYWxlIHByaWNlIGFsc28gaW5jcmVhZXMNCg0KSG93ZXZlciwgdGhlc2UgZ3JhcGhzIGFyZSBjbHV0dGVyZWQgaW4gdGhhdCBhbGwgZml2ZSBib3JvdWdocyBhcmUgc2hvd24uICANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IE5ZQ19zbV9tZF90d29fc3RvcnksDQogICAgICAgYWVzKHggPSBncm9zc19zcXVhcmVfZmVldCwgeSA9IHNhbGVfcHJpY2UpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMpICsNCiAgZmFjZXRfd3JhcCh+IGJvcm91Z2gsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlNpbmdsZSBGYW1pbHkgSG9tZSBTYWxlIFByaWNlIGluY3JlYXNlcyB3aXRoIFNpemUiLA0KICAgICAgIHggPSAiU2l6ZSAoR3Jvc3MgU3F1YXJlIEZlZXQpIiwNCiAgICAgICB5ID0gIlNhbGUgUHJpY2UgKFVTRCkiKQ0KYGBgDQoNCkZvciBlYWNoIGJvcm91Z2ggd2Ugc2VlIHRoYXQgaG9tZXMgd2l0aCBsYXJnZXIgZ3Jvc3Mgc3F1YXJlIGZlZXQgaGF2ZSBoaWdoZXIgc2FsZSBwcmljZXMNCg0KIyBPdXRsaWVycyBhbmQgRGF0YSBJbnRlZ3JpdHkgSXNzdWVzDQoNClRvIHNlZSBhbnkgb3V0bGllcnMgd2UgZmlyc3QgbmVlZCB0byBzb3J0IHNhbGUgcHJpY2VzIGJ5IGhpZ2hlc3QgdG8gbG93ZXN0DQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnkgJT4lIGFycmFuZ2UoZGVzYyhzYWxlX3ByaWNlKSkgJT4lIGhlYWQoKQ0KYGBgDQoNCjUgb2YgdGhlIHRvcCBzZWxsZXJzIGFyZSBpbiBCcm9va2x5biB3aXRoIG9uZSBpbiBRdWVlbnMuICBUaGUgaGlnaGVzdCBwcmljZSBvZiBcJDksMDAwLDAwMCB3YXMgbW9zdGx5IGxhbmQgKGxvb2tpbmcgb24gcmVhbHRvci5jb20gZG9lc24ndCBzaG93IGEgaG91c2UgYnV0IHppbGxvdy5jb20gZG9lcyBzaG93IGEgaG91c2UgdGhlcmUpDQoNCmBgYHtyfQ0KTllDX3NtX21kX3R3b19zdG9yeSAlPiUgZmlsdGVyKGJvcm91Z2ggPT0gIkJyb29rbHluIikgJT4lIGFycmFuZ2UoZGVzYyhncm9zc19zcXVhcmVfZmVldCkpICU+JSBoZWFkKCkNCmBgYA0KVGhlIGxhcmdlc3QgZ3Jvc3Mgc3F1YXJlIGZvb3QgcHJvcGVydHkgd2FzIGNvbnZlcnRlZCBmcm9tIGEgMyBmYW1pbGl5IGFwYXJ0bWVudCB0byBhIHNpbmdsZSBmYW1pbHkgaG9tZQ0KDQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnkgJT4lIGFycmFuZ2UoZGVzYyhncm9zc19zcXVhcmVfZmVldCkpICU+JSBoZWFkKCkNCmBgYA0KDQpIb3dldmVyIGxvb2tpbmcgYXQgZ3Jvc3Mgc3F1YXJlIGZlZXQgaGVyZSBzaG93cyBhIGRpZmZlcmVudCBzdG9yeSBidXQgdGhlbiBpdCBkZXBlbmRzIG9uIHRoZSBib3JvdWdoIHRoZSBwcm9wZXJ0eSBpcyBpbi4NCg0KV2Ugd2lsbCBub3QgcmVtb3ZlIHRoZSBvdXRsaWVyIGZvciBub3cgYXMgd2Ugd2FudCB0byBjb25maXJtIHRoZSBzYWxlIGlzIGxlZ2l0aW1hdGUNCg0KIyBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCBmb3IgQm9yb3VnaHMgaW4gTmV3IFlvcmsgQ2l0eSBDb21iaW5lZA0KDQpXZSB3aWxsIG5vdyBnZW5lcmF0ZSBhIGxpbmVhciBtb2RlbCB1c2luZyBgc2FsZV9wcmljZWAgZXhwbGFpbmVkIGJ5IGBncm9zc19zcXVhcmVfZmVldGAgYW5kIGBsYW5kX3NxdWFyZV9mZWV0YA0KDQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnlfbG0gPC0gbG0oc2FsZV9wcmljZSB+IGdyb3NzX3NxdWFyZV9mZWV0ICsgbGFuZF9zcXVhcmVfZmVldCwgZGF0YSA9IE5ZQ19zbV9tZF90d29fc3RvcnkpDQoNCnN1bW1hcnkoTllDX3NtX21kX3R3b19zdG9yeV9sbSkNCmBgYA0KDQpJZiB3ZSByZW1vdmUgdGhlIGhpZ2hlc3QgbGlzdGluZw0KYGBge3J9DQpOWUNfc21fbWRfdHdvX3N0b3J5X21vZGlmaWVkIDwtIE5ZQ19zbV9tZF90d29fc3RvcnkgJT4lIGZpbHRlcihhZGRyZXNzICE9ICI3MDQgQXZlbnVlIEkiKQ0KYGBgDQoNCmBgYHtyfQ0KTllDX3NtX21kX3R3b19zdG9yeV9tb2RpZmllZF9sbSA8LSBsbShzYWxlX3ByaWNlIH4gZ3Jvc3Nfc3F1YXJlX2ZlZXQgKyBsYW5kX3NxdWFyZV9mZWV0LCBkYXRhID0gTllDX3NtX21kX3R3b19zdG9yeV9tb2RpZmllZCkNCg0Kc3VtbWFyeShOWUNfc21fbWRfdHdvX3N0b3J5X21vZGlmaWVkX2xtKQ0KYGBgDQoNClJlbW92aW5nIHRoZSBoaWdoZXN0IGxpc3RpbmcgY2F1c2VkIGEgcmVkdWN0aW9uIGluIHRoZSBlc3RpbWF0ZSBmb3IgZ3Jvc3Nfc3F1YXJlX2ZlZXQgYnV0IGluY3JlYXNlZCBsYW5kX3NxdWFyZV9mZWV0LiAgRm9yIHN0YW5kYXJkIGVycm9yIGl0IHdhcyByZWR1Y2VkIGZvciBib3RoIGdyb3NzX3NxdWFyZV9mZWV0IGFuZCBsYW5kX3NxdWFyZV9mZWV0DQoNClJlcGxvdHRpbmcgdXNpbmcgZmFjZXQgd3JhcCB3aXRob3V0IHRoZSBvdXRsaWVyDQpgYGB7cn0NCmdncGxvdChkYXRhID0gTllDX3NtX21kX3R3b19zdG9yeV9tb2RpZmllZCwNCiAgICAgICBhZXMoeCA9IGdyb3NzX3NxdWFyZV9mZWV0LCB5ID0gc2FsZV9wcmljZSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMykgKw0KICBmYWNldF93cmFwKH4gYm9yb3VnaCwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gMikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiU2luZ2xlIEZhbWlseSBIb21lIFNhbGUgUHJpY2UgaW5jcmVhc2VzIHdpdGggU2l6ZSIsDQogICAgICAgeCA9ICJTaXplIChHcm9zcyBTcXVhcmUgRmVldCkiLA0KICAgICAgIHkgPSAiU2FsZSBQcmljZSAoVVNEKSIpDQpgYGANCg0KIyBMaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBmb3IgZWFjaCBib3JvdWdoIC0gQ29lZmZpY2llbnQgRXN0aW1hdGVzDQoNCldlIGhhdmUgYSBsaW5lYXIgbW9kZWwgZm9yIGFsbCBvZiB0aGUgYm9yb3VnaHMgYnV0IHdvdWxkIGJlIG1vcmUgdXNlZnVsIGlzIGlmIHdlIGhhZCBhIG1vZGVsIGZvciBlYWNoIG9uZS4gIFdlIHdpbGwgdXNlIHRoZSBkYXRhc2V0IHRoYXQgY29udGFpbnMgdGhlIG91dGxpZXIgYXMgdGhhdCBpcyBzdGlsbCBhIHNhbGUgKGFuZCB3YXMgY29uZmlybWVkIHRocm91Z2ggcmVhbHRvci5jb20pDQoNCldlIHdpbGwgdXNlIHRoZSBgYnJvb21gIHdvcmtmbG93IHdoaWNoIGlzIHRoZSBmb2xsb3dpbmc6DQoNCjEuIE5lc3QgYSBkYXRhZnJhbWUgYnkgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB3aXRoIHRoZSBgbmVzdCgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgdGlkeXJgIHBhY2thZ2UgLSB3ZSB3aWxsIG5lc3QgYnkgYGJvcm91Z2hgLg0KMi4gRml0IG1vZGVscyB0byBuZXN0ZWQgZGF0YWZyYW1lcyB3aXRoIHRoZSBgbWFwKClgIGZ1bmN0aW9uIGZyb20gdGhlIGBwdXJycmAgcGFja2FnZS4NCjMuIEFwcGx5IHRoZSBgYnJvb21gIGZ1bmN0aW9ucyBgdGlkeSgpYCwgYGF1Z21lbnQoKWAsIGFuZC9vciBgZ2xhbmNlKClgIHVzaW5nIGVhY2ggbmVzdGVkIG1vZGVsIC0gd2UnbGwgd29yayB3aXRoIGB0aWR5KClgIGZpcnN0Lg0KNC4gUmV0dXJuIGEgdGlkeSBkYXRhZnJhbWUgd2l0aCB0aGUgYHVubmVzdCgpYCBmdW5jdGlvbiAtIHRoaXMgYWxsb3dzIHVzIHRvIHNlZSB0aGUgcmVzdWx0cy4NCg0KYGBge3J9DQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkocHVycnIpDQpgYGANCmBgYHtyfQ0KTllDX3NtX21kX3R3b19zdG9yeV9uZXN0ZWQgPC0gTllDX3NtX21kX3R3b19zdG9yeSAlPiUgZ3JvdXBfYnkoYm9yb3VnaCkgJT4lIG5lc3QoKQ0KcHJpbnQoTllDX3NtX21kX3R3b19zdG9yeV9uZXN0ZWQpDQpgYGANCg0KTG9va2luZyBhdCBNYW5oYXR0YW4gd2UgaGF2ZQ0KYGBge3J9DQpoZWFkKE5ZQ19zbV9tZF90d29fc3RvcnlfbmVzdGVkJGRhdGFbWzNdXSkNCmBgYA0KDQpXZSB3aWxsIG5vdyBmaXQgYSBsaW5lYXIgbW9kZWwgdG8gZWFjaCBkYXRhZnJhbWUgKGJvcm91Z2gpDQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnlfY29lZmZpY2llbnRzIDwtIE5ZQ19zbV9tZF90d29fc3RvcnkgJT4lIGdyb3VwX2J5KGJvcm91Z2gpICU+JSBuZXN0KCkgJT4lDQogIG11dGF0ZShsaW5lYXJfbW9kZWwgPSBtYXAoLnggPSBkYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5mID0gfmxtKHNhbGVfcHJpY2UgfiBncm9zc19zcXVhcmVfZmVldCArIGxhbmRfc3F1YXJlX2ZlZXQsIGRhdGEgPSAuKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpDQpgYGANCg0KYGBge3J9DQpwcmludChOWUNfc21fbWRfdHdvX3N0b3J5X2NvZWZmaWNpZW50cykNCmBgYA0KDQpMb29raW5nIGF0IHRoZSBzdW1tYXJ5IGZvciBNYW5oYXR0YW4NCmBgYHtyfQ0Kc3VtbWFyeShOWUNfc21fbWRfdHdvX3N0b3J5X2NvZWZmaWNpZW50cyRsaW5lYXJfbW9kZWxbWzNdXSkNCmBgYA0KDQpUaGF0IGlzIGJlY2F1c2UgdGhlcmUgaXMgb25seSBhIHNpbmdsZSBlbnRyeSBmb3IgTWFuaGF0dGFuIGxvb2tpbmcgYXQgQnJvb2tseW4NCmBgYHtyfQ0Kc3VtbWFyeShOWUNfc21fbWRfdHdvX3N0b3J5X2NvZWZmaWNpZW50cyRsaW5lYXJfbW9kZWxbWzJdXSkNCmBgYA0KDQpUcmFuc2Zvcm1pbmcgdGhpcyBkYXRhZnJhbWUgaW50byBhIHRpZHkgZm9ybWF0DQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnlfY29lZmZpY2llbnRzIDwtIE5ZQ19zbV9tZF90d29fc3RvcnkgJT4lIGdyb3VwX2J5KGJvcm91Z2gpICU+JSBuZXN0KCkgJT4lDQogIG11dGF0ZShsaW5lYXJfbW9kZWwgPSBtYXAoLnggPSBkYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5mID0gfmxtKHNhbGVfcHJpY2UgfiBncm9zc19zcXVhcmVfZmVldCArIGxhbmRfc3F1YXJlX2ZlZXQsIGRhdGEgPSAuKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpICU+JQ0KICBtdXRhdGUodGlkeV9jb2VmZmljaWVudHMgPSBtYXAoLnggPSBsaW5lYXJfbW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZiA9IHRpZHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25mLmludCA9IFRSVUUpKQ0KDQpOWUNfc21fbWRfdHdvX3N0b3J5X2NvZWZmaWNpZW50cw0KYGBgDQoNCkZpcnN0IGluc3BlY3QgZm9yIEJyb29rbHluDQpgYGB7cn0NCnByaW50KE5ZQ19zbV9tZF90d29fc3RvcnlfY29lZmZpY2llbnRzJHRpZHlfY29lZmZpY2llbnRzW1syXV0pDQpgYGANCg0KDQpBbmQgbGV0J3MgZ2V0IGEgdGlkeSBkYXRhZnJhbWUgb3V0IHVzaW5nIGB1bmVzdCgpYA0KDQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3RvcnlfdGlkeSA8LSBOWUNfc21fbWRfdHdvX3N0b3J5X2NvZWZmaWNpZW50cyAlPiUgDQogIHNlbGVjdChib3JvdWdoLCB0aWR5X2NvZWZmaWNpZW50cykgJT4lIA0KICB1bm5lc3QoY29scyA9IHRpZHlfY29lZmZpY2llbnRzKQ0KDQpwcmludChOWUNfc21fbWRfdHdvX3N0b3J5X3RpZHkpDQpgYGANCg0KR3Jvc3Mgc3F1YXJlIGZlZXQgaGFzIGEgaGlnaGVyIGVzdGltYXRlIGJ1dCBvdXIgbW9kZWxzIGluY2x1ZGUgYm90aCBhcyB0aGVzZSBhcmUgaG9tZXMgYW5kIG5vdCBjb25kb21pbml1bXMuICBIb3dldmVyLCB3ZSB3aWxsIHVzZSBncm9zcyBzcXVhcmUgZmVldCBhcyB0aGF0IGhhcyBhIGhpZ2hlciBlc3RpbWF0ZQ0KDQpgYGB7cn0NCk5ZQ19zbV9tZF90d29fc3Rvcnlfc2xvcGUgPC0gTllDX3NtX21kX3R3b19zdG9yeV90aWR5ICU+JSBmaWx0ZXIodGVybSA9PSAiZ3Jvc3Nfc3F1YXJlX2ZlZXQiKSAlPiUgYXJyYW5nZShlc3RpbWF0ZSkNCnByaW50KE5ZQ19zbV9tZF90d29fc3Rvcnlfc2xvcGUpDQpgYGANCg0KVGhlIGhpZ2hlc3QgZXN0aW1hdGUgZm9yIGdyb3NzIHNxdWFyZSBmZWV0IGlzIGluIEJyb29rbHluICh3aGljaCBhbHNvIGhhcyB0aGUgaGlnaGVzdCBzYWxlIHByaWNlKQ0KDQpMb29raW5nIGF0IGxhbmQgc3F1YXJlIGZlZXQNCmBgYHtyfQ0KTllDX3NtX21kX3R3b19zdG9yeV9zbG9wZV9sYW5kIDwtIE5ZQ19zbV9tZF90d29fc3RvcnlfdGlkeSAlPiUgZmlsdGVyKHRlcm0gPT0gImxhbmRfc3F1YXJlX2ZlZXQiKSAlPiUgYXJyYW5nZShlc3RpbWF0ZSkNCnByaW50KE5ZQ19zbV9tZF90d29fc3Rvcnlfc2xvcGVfbGFuZCkNCmBgYA0KDQpUaGlzIGFsc28gY29uZmlybXMgdGhhdCBicm9va2x5biBoYXMgdGhlIGhpZ2hlc3QgZXN0aW1hdGUgZm9yIGxhbmQgc3F1YXJlIGZlZXQNCg0KIyBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbHMgZm9yIGVhY2ggQm9yb3VnaCAtIFJlZ3Jlc3Npb24gU3VtbWFyeSBTdGF0aXN0aWNzDQoNCldlIHdpbGwgYXBwbHkgdGhlIHNhbWUgd29ya2Zsb3cgdG8gZ2VuZXJhdGUgdGlkeSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGVhY2ggb2YgdGhlIGZpdmUgYm9yb3VnaHMNCmBgYHtyfQ0KTllDX3NtX21kX3R3b19zdG9yeV9zdW1tYXJ5IDwtIE5ZQ19zbV9tZF90d29fc3RvcnkgJT4lIA0KICBncm91cF9ieShib3JvdWdoKSAlPiUNCiAgbmVzdCgpICU+JSBtdXRhdGUobGluZWFyX21vZGVsID0gbWFwKC54ID0gZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5mID0gfmxtKHNhbGVfcHJpY2UgfiBncm9zc19zcXVhcmVfZmVldCArIGxhbmRfc3F1YXJlX2ZlZXQsIGRhdGEgPSAuKSkpICU+JQ0KICBtdXRhdGUodGlkeV9zdW1tYXJ5X3N0YXRzID0gbWFwKC54ID0gbGluZWFyX21vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5mID0gZ2xhbmNlKSkNCg0KcHJpbnQoTllDX3NtX21kX3R3b19zdG9yeV9zdW1tYXJ5KQ0KICANCmBgYA0KDQpOb3cgbGV0J3MgdXNlIHRoZSBgdGlkeV9zdW1tYXJ5X3N0YXRzYCB0byBnZXQgb3VyIGNvbmNsdXNpb24NCmBgYHtyfQ0KTllDX3NtX21kX3R3b19zdG9yeV90aWR5IDwtIE5ZQ19zbV9tZF90d29fc3Rvcnlfc3VtbWFyeSAlPiUgDQogIHNlbGVjdChib3JvdWdoLCB0aWR5X3N1bW1hcnlfc3RhdHMpICU+JQ0KICB1bm5lc3QoY29scyA9IHRpZHlfc3VtbWFyeV9zdGF0cykgJT4lDQogIGFycmFuZ2Uoci5zcXVhcmVkKQ0KDQpwcmludChOWUNfc21fbWRfdHdvX3N0b3J5X3RpZHkpDQpgYGANCg0KIyBDb25jbHVzaW9uDQoNCldlIGNhbiBzZWUgdGhhdCBncm9zcyBzcXVhcmUgZmVldCBhbmQgbGFuZCBzcXVhcmUgZmVldCBhcmUgZ29vZCBwcmVkaWN0b3JzIG9mIHNhbGVzIHByaWNlcyB3aXRoIHRoZSBoaWdoZXN0IFIgc3F1YXJlZCB2YWx1ZSBpbiBCcm9va2x5biBmb2xsb3dlZCBieSBTdGF0ZW4gSXNsYW5kIGFuZCB0aGUgQnJvbnguICBUaGlzIGlzIHRoZSBsb3dlciBpbiBRdWVlbnMgYW5kIE1hbmhhdHRhbiBoYXMgYSB2YWx1ZSBvZiAwIHNpbXBseSBiZWNhdXNlIHRoZXJlIHdhcyBhIHNpbmdsZSBBMSBwcm9wZXJ0eSBzYWxlIHRoZXJlLg0KDQo=